;; supercollider/graphdef.scm - (c) rohan drape, 2003-2005

;; A <graphdef> is the static representation of a UGen graph.  This
;; data can be read and written as bytecode strings.

(define-structure graphdef (name
			    constants
			    control-defaults
			    controls
			    ugens))

;; Accessor for indexed ugen `n' at the graphdef `g'.

(define (graphdef-ugen-ref g n) (ref (graphdef-ugens g) n))

;; Accessor for indexed control `n' at the graphdef `g'.

(define (graphdef-control-ref g n) (ref (graphdef-controls g) n))

;; Accessor for indexed constant `n' at the graphdef `g'.

(define (graphdef-constant-ref g n)
  (if (>= n (length (graphdef-constants g)))
      (error "graphdef-constant-ref: illegal index" g n)
      (quantize (ref (graphdef-constants g) n) 0.001)))

;; Read a 'Graph Definition' from `port'.  Although an SC3 'Synth
;; Definition' file may contain more than one graph definition, we do
;; not support such files here.

(define (graphdef-read port)
  (let* ((magic (read-string 4 port))
	 (version (read-int32 port))
	 (number-of-definitions (read-int16 port)))
    (if (not (string= magic "SCgf"))
	(error "graphdef-read: illegal magic string" magic))
    (if (not (= version 0))
	(error "graphdef-read: version not at zero" version))
    (if (not (= number-of-definitions 1))
	(error "graphdef-read: non unary graphdef file" number-of-definitions))
    (let* ((name (read-pascal-string port))
	   (number-of-constants (read-int16 port))
	   (constants (list-read number-of-constants 
				 read-float32 
				 port))
	   (number-of-control-defaults (read-int16 port))
	   (control-defaults (list-read 
			      number-of-control-defaults 
			      read-float32 
			      port))
	   (number-of-controls (read-int16 port))
	   (controls (list-read number-of-controls 
				control-read
				port))
	   (number-of-ugens (read-int16 port))
	   (ugens (list-read number-of-ugens
			     ugen-read
			     port)))
      (make-graphdef name
		     constants
		     control-defaults
		     controls
		     ugens))))

;; Parse the bytecode <u8vector> `b' to a graphdef record.

(define (graphdef-decode b)
  (let ((p (open-input-u8vector b)))
    (graphdef-read p)))

;; Write the 'Graph Definition' `g' to `port' as a single element
;; 'Synth Definition' file.

(define (graphdef-write g port)
  (display "SCgf" port)
  (write-int32 0 port)
  (write-int16 1 port)
  (write-pascal-string (graphdef-name g) port)
  (write-int16 (length (graphdef-constants g)) port)
  (list-write (graphdef-constants g) write-float32 port)
  (write-int16 (length (graphdef-control-defaults g)) port)
  (list-write (graphdef-control-defaults g) write-float32 port)
  (write-int16 (length (graphdef-controls g)) port)
  (list-write (graphdef-controls g) control-write port)
  (write-int16 (length (graphdef-ugens g)) port)
  (list-write (graphdef-ugens g) ugen-write port))

;; This currently fails because (make-u8vector 0) fails.
;; Interestingly (u8vector) works...

(define (graphdef-encode_ g)
  (u8vector-append
   (string->u8vector "SCgf")
   (encode-int32 0)
   (encode-int16 1)
   (encode-pascal-string (graphdef-name g))
   (encode-int16 (length (graphdef-constants g)))
   (u8vector-concatenate (map encode-float32 (graphdef-constants g)))
   (encode-int16 (length (graphdef-control-defaults g)))
   (u8vector-concatenate (map encode-float32 (graphdef-control-defaults g)))
   (encode-int16 (length (graphdef-controls g)))
   (u8vector-concatenate (map control-encode (graphdef-controls g)))
   (encode-int16 (length (graphdef-ugens g)))
   (u8vector-concatenate (map ugen-encode (graphdef-ugens g)))))

;; Evaluate to the bytecode <u8vector> for the 'Graph Definition'
;; `g'.

(define (graphdef-encode g)
  (let ((b (open-output-u8vector)))
    (graphdef-write g b)
    (get-output-u8vector b)))

;; Read a 'Graph Definition' stored in the file at `file-name'.

(define (graphdef-read-file file-name)
  (call-with-input-file file-name
    (lambda (input-port)
      (graphdef-read input-port))))

;; Write the 'Graph Definition' `s' to the file `file-name'.
;; Overwrite any existing file.

(define (graphdef-write-file s file-name)
  (if (file-exists? file-name)
      (unlink file-name))
  (call-with-output-file file-name
    (lambda (output-port)
      (graphdef-write s output-port))))

;; Load the named graphdef file.

(define (string->graphdef s) 
  (graphdef-read-file (synthdef-file-name s)))

;; Decode the <u8vector> `b' to a graphdef.

(define (u8vector->graphdef b) 
  (graphdef-decode b))

;; Conversion

(define-generic as-graphdef)
(define-method (as-graphdef (g <graphdef>)) g)
(define-method (as-graphdef (p <thunk>)) (p))
(define-method (as-graphdef (s <String>)) (string->graphdef s))
(define-method (as-graphdef (s <u8vector>)) (u8vector->graphdef s))
